package com.jnc.rest.core.interceptor;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.jnc.rest.biz.common.model.UriAuth;
import com.jnc.rest.biz.common.service.DictService;
import com.jnc.rest.constant.BizConstant;
import com.jnc.rest.constant.RedisConstant;
import com.jnc.rest.constant.SysConstant;
import com.jnc.rest.core.base.dto.BaseResp;
import com.jnc.rest.core.base.dto.RespCode;
import com.jnc.rest.core.config.redis.RedisStore;
import com.jnc.rest.util.JsonUtil;
import com.jnc.rest.util.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;

/**
 * @Author: jjn
 * @Date: 2020/1/6 10:28
 * @Description: 访问认证拦截器
 */
@Slf4j
public class AccessTokenInterceptor extends HandlerInterceptorAdapter {

    private RedisStore redisStore;
    private TokenUtil tokenUtil;
    private DictService dictService;
    private long timeout = 30 * 60;

    public AccessTokenInterceptor(RedisStore redisStore, TokenUtil tokenUtil, long timeout, DictService dictService){
        this.redisStore = redisStore;
        this.tokenUtil = tokenUtil;
        this.timeout = timeout;
        this.dictService = dictService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if("OPTIONS".equals(request.getMethod())){
            return super.preHandle(request, response, handler);
        }

        BaseResp resp = new BaseResp();
        String token = request.getHeader("Authorization");
        log.info("请求接口：{}, 请求IP：{}, 请求token：{}", request.getRequestURI(), getIpAddress(request), token);
        //1、判断该token是否被踢出
        boolean isKick = isKick(request);
        if(isKick){
            log.warn("该账号已在其他地方登录，该账号已下线, 请求接口：{}，请求IP：{}，请求参数：{}",
                    request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));
            resp.setCode(RespCode.KICK_LOGIN.getCode()).setMsg("您的账号已在其它地方登陆，若不是本人操作，请注意账号安全");
            responseResult(response, resp);
            return false;
        }
        //2、判断url路径是否需要token认证
        String urlPath = request.getServletPath();
        boolean flag = checkUrl(urlPath);
        if(flag){
            if(validateSign(request)){
                //自动续签
                renewToken(token);
                return true;
            }else{
                log.warn("签名认证失败，请求接口：{}，请求IP：{}，请求参数：{}",
                        request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));
                resp.setCode(401).setMsg("Invalid access token: " + token);
                responseResult(response, resp);
                return false;
            }
        }
        return true;
    }

    /**
     * 写出响应数据
     * @param response
     * @param resp
     */
    private void responseResult(HttpServletResponse response, BaseResp resp){
        response.setCharacterEncoding(SysConstant.CHARSET_NAME);
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setStatus(SysConstant.HTTP_CODE_SUCCESS);
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            writer.print(JsonUtil.bean2Json(resp));
            writer.flush();
        } catch (IOException e) {
            log.error(e.getMessage());
        } finally {
            if (writer != null){
                writer.close();
            }
        }
    }

    /**
     * 续签(当token剩余时间小于总时长的三分之一，就自动续签token)
     * @param token
     */
    private void renewToken(String token){
        long duration = tokenUtil.getDuration(token);
        if(duration > 0 && duration * 3 < timeout){
            String tokenKey = tokenUtil.genTokenUserKey(token);
            String userName = tokenUtil.getUserNameBy(token);
            String userKey = tokenUtil.genUserTokenKey(userName);
            redisStore.expire(userKey, timeout);
            redisStore.expire(tokenKey, timeout);
        }
    }

    /**
     * 判断该token是否被踢出登录
     * @param request
     * @return
     */
    private boolean isKick(HttpServletRequest request){
        boolean isKick = false;
        String token = request.getHeader("Authorization");
        if(StringUtils.isNotBlank(token)){
            //判断该token是否被踢出
            String key = tokenUtil.genOffTokenUserKey(token);
            isKick = redisStore.exists(key);
            //当判断被踢出之后，则删除踢人缓存
            if(isKick){
                redisStore.del(key);
            }
        }
        return isKick;
    }

    /**
     * 判断url路径是否需要token认证
     * 认证配置规则：
     * 1、指定uri接口，表sys_uri_auth不配置参数，默认是该uri接口，不需要token认证的。
     * 2、配置支持通配符配置，配置规则是：?表示单个字符、*表示一层路径内的任意字符串，不可跨层级、**表示任意层路径。
     * 3、配置支持需要token认证和不需要token认证[is_auth：是否认证(0:是；1:否)]，优先级：不需要token认证 > 需要token认证。
     * @param urlPath
     * @return
     */
    private boolean checkUrl(String urlPath){
        List<Object> list = redisStore.getList(RedisConstant.URI_AUTH_DATA, 0, -1);
        if(CollectionUtil.isEmpty(list)){ //为空则查询数据库
            List<UriAuth> uriAuthList = dictService.queryUri();
            if(uriAuthList != null && uriAuthList.size() > 0){
                redisStore.del(RedisConstant.URI_AUTH_DATA);
                Iterator<UriAuth> it = uriAuthList.iterator();
                while (it.hasNext()){
                    redisStore.leftPush(RedisConstant.URI_AUTH_DATA, JsonUtil.bean2Json(it.next()));
                }
                list = redisStore.getList(RedisConstant.URI_AUTH_DATA, 0, -1);
            }
        }

        //不为空，则uri进行匹配
        if(CollectionUtil.isNotEmpty(list)){
            AntPathMatcher matcher = new AntPathMatcher();
            Iterator<Object> it = list.iterator();
            while (it.hasNext()){
                String next = (String) it.next();
                UriAuth uriAuth = JsonUtil.json2Bean(next, UriAuth.class);
                boolean match = matcher.match(uriAuth.getUri(), urlPath);
                if(match){
                    if(BizConstant.IS_AUTH_YES.equals(uriAuth.getIsAuth())){
                        return true;
                    }
                    return false;
                }
            }
        }
        return false;
    }

    /**
     * 认证url中，token参数的有效性
     * @param request
     * @return
     */
    private boolean validateSign(HttpServletRequest request){
        //获去token
        String token = request.getHeader("Authorization");
        if(StringUtils.isEmpty(token)){
            return false;
        }
        String key = tokenUtil.genTokenUserKey(token);
        return redisStore.exists(key);
    }


    /**
     * 获取请求ip地址
     * @param request
     * @return
     */
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 如果是多级代理，那么取第一个ip为客户端ip
        if (ip != null && ip.indexOf(",") != -1) {
            ip = ip.substring(0, ip.indexOf(",")).trim();
        }
        return ip;
    }
}
